//
// Created by guoshunw on 24-6-4.
//

#include <iostream>
#include <cstring>
#include "coroutine.h"
#include "log.h"

Schedule::Schedule() {
    LOGD << "Create schedule@" << this;
}

Schedule::~Schedule() {
    coroutines.clear();
    LOGD << "Destroy schedule@" << this;
}

Coroutine::Coroutine(Schedule *schedule, const std::function<int(Coroutine *)> &fun, const void *userData)
        : fun(fun), userData(userData), sch(schedule), id(getNextId()) {
    LOGD << "Create coroutine@" << this;
}

Coroutine::~Coroutine() {
    LOGD << "Destroy coroutine@" << this;
    free(stack);
    stack = nullptr;
}

void Coroutine::saveStack(const char *stack_top) {
    char dummy{};
    auto stack_size = stack_top - &dummy;
    assert(stack_size <= STACK_SIZE);
    if (capacity < stack_size) {
        LOGD << "Extend coroutine " << id << " stack capacity from " << capacity << " to " << stack_size
             << " bytes.";
        delete[] static_cast<char *>(stack);
        stack = new char[stack_size];
        capacity = stack_size;
    }
    size = stack_size;
    memcpy(stack, &dummy, stack_size);
}

void Coroutine::yield() {
    if (sch->running == -1) {
        LOGW << "No coroutine is running...";
        return;
    }
    if (CoroutineStatus::RUNNING != status) {
        LOGW << "Coroutine " << id << " is not running, cannot yield...";
        return;
    }

    // save stack
    saveStack(sch->stack + STACK_SIZE);
    status = CoroutineStatus::SUSPENDED;
    sch->running = -1;
    swapcontext(&ctx, &sch->main);
}


void Coroutine::resume() {
    if (sch->running != -1) {
        LOGW << "Coroutine " << sch->running << " is running...";
        return;
    }
    switch (status) {
        case CoroutineStatus::READY: {
            getcontext(&ctx);

            ctx.uc_stack.ss_sp = sch->stack;
            ctx.uc_stack.ss_size = STACK_SIZE;
            ctx.uc_link = &sch->main;

            status = CoroutineStatus::RUNNING;
            sch->running = id;
            makecontext(&ctx, reinterpret_cast<void (*)()>(mainFun), 2, sch, this);
            swapcontext(&sch->main, &ctx);
            break;
        }
        case CoroutineStatus::SUSPENDED: {
            memcpy(sch->stack + STACK_SIZE - size, stack, size);
            sch->running = id;
            status = CoroutineStatus::RUNNING;
            swapcontext(&sch->main, &ctx);
            break;
        }
        default:
            LOGE << "Incorrect coroutine status: " << static_cast<int>(status);
            break;
    }


}

long Coroutine::getId() const {
    return id;
}

void Coroutine::mainFun(Schedule *schedule, Coroutine *co) {
    co->fun(co);
    schedule->mainFun(co);
}

const void *Coroutine::getUserData() const {
    return userData;
}

void Schedule::mainFun(Coroutine *co) {
    assert(co);
    coroutines.erase(co->getId());
    running = -1;
}

std::shared_ptr<Coroutine> Schedule::addCoroutine(const std::function<int(Coroutine *)> &fun, const void *userData) {
    auto co = std::make_shared<Coroutine>(this, fun, userData);
    coroutines.emplace(co->getId(), co);
    return co;
}

